package ln.gather.jsr.jcache;

import ln.gather.jsr.common.util.Deserializers;
import ln.gather.jsr.common.util.Serializers;

import javax.cache.Cache;
import javax.cache.CacheException;
import javax.cache.CacheManager;
import javax.cache.configuration.Configuration;
import javax.cache.configuration.MutableConfiguration;
import javax.cache.spi.CachingProvider;
import java.net.URI;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Consumer;
import java.util.logging.Logger;

/**
 * @Description AbstractCacheManager
 * @Author zhangj
 * @Date 2022/11/16 22:53
 * @see ConfigurationCachingProvider
 */
public abstract class AbstractCacheManager implements CacheManager {

    private static final Consumer<Cache> CLEAR_CACHE_OPERATION = Cache::clear;

    private static final Consumer<Cache> CLOSE_CACHE_OPERATION = Cache::close;

    private final Logger logger = Logger.getLogger(this.getClass().getName());

    private final CachingProvider cachingProvider;

    private final URI uri;

    private final ClassLoader classLoader;

    private final Serializers serializers;

    private final Deserializers deserializers;

    private final Properties properties;

    private volatile boolean closed;

    private ConcurrentMap<String, Map<KeyValueTypePair, Cache>> cacheRepository = new ConcurrentHashMap<>();

    public AbstractCacheManager(CachingProvider cachingProvider, URI uri, ClassLoader classLoader, Properties properties) {
        this.cachingProvider = cachingProvider;
        this.uri = uri;
        this.classLoader = classLoader;
        this.serializers = initSerializers(classLoader);
        this.deserializers = initDeserializers(classLoader);
        this.properties = properties;
    }

    protected Serializers initSerializers(ClassLoader classLoader) {
        Serializers serializers = new Serializers(classLoader);
        serializers.loadSPI();
        return serializers;
    }

    protected Deserializers initDeserializers(ClassLoader classLoader) {
        Deserializers deserializers = new Deserializers(classLoader);
        deserializers.loadSPI();
        return deserializers;
    }

    @Override
    public CachingProvider getCachingProvider() {
        return cachingProvider;
    }

    @Override
    public URI getURI() {
        return uri;
    }

    @Override
    public ClassLoader getClassLoader() {
        return classLoader;
    }

    @Override
    public Properties getProperties() {
        return properties;
    }

    @Override
    public <K, V, C extends Configuration<K, V>> Cache<K, V> createCache(String cacheName, C configuration) throws IllegalArgumentException {
        // If a Cache with the specified name is known to the CacheManger, a CacheException is thrown.
        if (!cacheRepository.getOrDefault(cacheName, Collections.emptyMap()).isEmpty()) {
            throw new CacheException(String.format("The Cache whose name is '%s' is already existed, please try another name to create a new Cache.", cacheName));
        }
        // If a Cache with the specified name is unknown the CacheManager, one is created according to
        // the provided Configuration after which it becomes managed by the CacheManager.
        return getOrCreateCache(cacheName, configuration, true);
    }

    /**
     * 获取缓存
     *
     * @param cacheName     缓存名称
     * @param configuration 缓存配置
     * @param created       如果不存在是否新建
     * @return
     * @see Cache
     */
    protected <V, K, C extends Configuration<K, V>> Cache<K, V> getOrCreateCache(String cacheName, C configuration, boolean created) {
        // 检查缓存管理器是否被关闭
        assertNotClosed();
        // 从缓存仓库中获取该缓存key 对应的缓存Map，如果不存在则新建
        Map<KeyValueTypePair, Cache> cacheMap = cacheRepository.computeIfAbsent(cacheName, n -> new ConcurrentHashMap<>());
        // 获取缓存中该键值类型对对应的数据，如果不存在根据 created 来判断是否要创建新的数据
        return cacheMap.computeIfAbsent(new KeyValueTypePair(configuration.getKeyType(), configuration.getValueType()),
                key -> created ? doCreateCache(cacheName, configuration) : null);
    }

    /**
     * 创建缓存数据，子类实现
     *
     * @param cacheName
     * @param configuration
     * @return
     */
    protected abstract <K, V, C extends Configuration<K, V>> Cache doCreateCache(String cacheName, C configuration);

    protected void assertNotClosed() {
        if (isClosed()) {
            throw new IllegalStateException("The CacheManager has been closed, current operation should not be invoked!");
        }
    }

    @Override
    public <K, V> Cache<K, V> getCache(String cacheName, Class<K> keyType, Class<V> valueType) {
        MutableConfiguration<K, V> configuration = new MutableConfiguration<K, V>().setTypes(keyType, valueType);
        return getOrCreateCache(cacheName, configuration, false);
    }

    @Override
    public <K, V> Cache<K, V> getCache(String cacheName) {
        return getCache(cacheName, (Class<K>) Object.class, (Class<V>) Object.class);
    }

    @Override
    public Iterable<String> getCacheNames() {
        assertNotClosed();
        return cacheRepository.keySet();
    }

    @Override
    public void destroyCache(String cacheName) {
        Objects.requireNonNull(cacheName, "The 'cacheName' argument must be null.");
        assertNotClosed();
        Map<KeyValueTypePair, Cache> cacheMap = cacheRepository.remove(cacheName);
        if (cacheMap != null) {
            iteratorCache(cacheMap.values(), CLEAR_CACHE_OPERATION, CLOSE_CACHE_OPERATION);
        }
    }

    protected final void iteratorCache(Iterable<Cache> caches, Consumer<Cache>... cacheOperations) {
        for (Cache cach : caches) {
            for (Consumer<Cache> cacheOperation : cacheOperations) {
                try {
                    cacheOperation.accept(cach);
                } catch (Throwable e) {
                    // just log, ignore the exception propagation
                    logger.finest(e.getMessage());
                }
            }
        }
    }

    @Override
    public void enableManagement(String cacheName, boolean enabled) {
        assertNotClosed();
        //todo ...
        throw new UnsupportedOperationException("To support in the future...");
    }

    @Override
    public void enableStatistics(String cacheName, boolean enabled) {
        assertNotClosed();
        //todo ...
        throw new UnsupportedOperationException("To support in the future...");
    }

    @Override
    public void close() {
        if (isClosed()) {
            logger.warning("...");
            return;
        }
        cacheRepository.values().forEach(c -> {
            iteratorCache(c.values(), CLOSE_CACHE_OPERATION);
        });
        doClose();
        this.closed = true;
    }

    /**
     * subclass may override this method
     */
    protected void doClose() {}

    @Override
    public boolean isClosed() {
        return this.closed;
    }

    @Override
    public <T> T unwrap(Class<T> clazz) {
        T value = null;
        try {
            value = clazz.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        return value;
    }
}
